package com.eternal.base.data.ble;

import android.bluetooth.BluetoothGatt;
import android.os.Handler;
import android.os.Looper;

import com.clj.fastble.BleManager;
import com.clj.fastble.callback.BleGattCallback;
import com.clj.fastble.callback.BleMtuChangedCallback;
import com.clj.fastble.callback.BleNotifyCallback;
import com.clj.fastble.callback.BleScanCallback;
import com.clj.fastble.callback.BleWriteCallback;
import com.clj.fastble.data.BleDevice;
import com.clj.fastble.data.BleScanState;
import com.clj.fastble.exception.BleException;
import com.clj.fastble.scan.BleScanRuleConfig;
import com.eternal.base.exception.NotCloseScanException;
import com.eternal.base.exception.NotEnableBluetoothException;
import com.eternal.base.global.BluetoothKey;
import com.eternal.base.protocol.CFamilialResolution;
import com.eternal.base.protocol.StateMachine;
import com.eternal.base.utils.ByteUtils;
import com.eternal.framework.utils.KLog;
import com.eternal.framework.utils.Utils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.Single;
import io.reactivex.SingleEmitter;
import io.reactivex.SingleOnSubscribe;
import io.reactivex.annotations.NonNull;

public class BleServer {
    private static BleServer INSTANCE;
    private final BleManager manager;
    private MainHandler mainHandler = new MainHandler(Looper.getMainLooper());
    public static final int MSG_CHA_READ_TIME = 0x10;
    private BleServer() {
        manager = BleManager.getInstance();
        manager.setOperateTimeout(100000);
    }

    private final class MainHandler extends Handler {

        MainHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(@androidx.annotation.NonNull android.os.Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case MSG_CHA_READ_TIME: {
                    if (msg.obj != null) {
                        BleStatue queue = (BleStatue)msg.obj;
                        KLog.e("read timeout  " + ByteUtils.bytes2HexString(queue.getPacket()));
                        write(queue);
                    }
                }
                break;
                }
        }
    }

    public static BleServer getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new BleServer();

        }
        return INSTANCE;
    }

    public void enable() {
        if (!manager.isBlueEnable()) {
            manager.enableBluetooth();
        }
    }

    public void reset() {
        manager.disconnectAllDevice();
        manager.destroy();
        manager.init(Utils.getApp());
    }

    public void clear(Collection<BleStatue> statues) {
        List<BleDevice> devices = manager.getAllConnectedDevice();
        Iterator<BleDevice> iter = devices.iterator();
        BleDevice temp;
        while (iter.hasNext()) {
            temp = iter.next();
            for (BleStatue statue : statues) {
                if (statue.getDevice().getMac().equals(temp.getMac())) {
                    iter.remove();
                    break;
                }
            }
        }
        for (BleDevice device : devices) {
            manager.disconnect(device);
        }
    }

    void closeScan() {
        KLog.i("cancel scan");
        if (manager.getScanSate() == BleScanState.STATE_SCANNING) {
            manager.cancelScan();
        }
    }

    public Single<List<BleDevice>> scan(final BleScanRuleConfig config) {
        return Single.create(new SingleOnSubscribe<List<BleDevice>>() {
            @Override
            public void subscribe(final SingleEmitter<List<BleDevice>> emitter) throws Exception {
                manager.initScanRule(config);
                manager.scan(new BleScanCallback() {
                    @Override
                    public void onScanFinished(List<BleDevice> scanResultList) {
                        emitter.onSuccess(scanResultList);
                    }

                    @Override
                    public void onScanStarted(boolean success) {
                        if (!success) {
                            if (manager.isBlueEnable()) {
                                emitter.onError(new NotCloseScanException());
                            } else {
                                emitter.onError(new NotEnableBluetoothException());
                            }
                        }
                    }

                    @Override
                    public void onScanning(BleDevice bleDevice) {
                    }
                });
            }
        });
    }

    public Observable<List<BleDevice>> scanCycle(final BleScanRuleConfig config) {
        return Observable.create(new ObservableOnSubscribe<List<BleDevice>>() {
            @Override
            public void subscribe(final @NonNull ObservableEmitter<List<BleDevice>> emitter) throws Exception {
                manager.initScanRule(config);
                manager.scan(new BleScanCallback() {
                    @Override
                    public void onScanFinished(List<BleDevice> scanResultList) {
                        emitter.onNext(scanResultList);
                    }

                    @Override
                    public void onScanStarted(boolean success) {
                        if (!success) {
                            if (manager.isBlueEnable()) {
                                emitter.onError(new NotCloseScanException());
                            } else {
                                emitter.onError(new NotEnableBluetoothException());
                            }
                        }
                    }

                    @Override
                    public void onScanning(BleDevice bleDevice) {
                        emitter.onNext(new ArrayList<>(Arrays.asList(bleDevice)));
                    }
                });
            }
        });
    }

    /**
     * 链接设备，断开事件和回复由RxBus发送。
     *
     * @param device 设备
     * @return
     */
    public Single<BleDevice> connect(final BleDevice device, final DisconnectListener listener) {
        return Single.create(new SingleOnSubscribe<BleDevice>() {
            @Override
            public void subscribe(final SingleEmitter<BleDevice> emitter) throws Exception {
                manager.connect(device, new BleGattCallback() {
                    @Override
                    public void onStartConnect() {
                    }

                    @Override
                    public void onConnectFail(BleDevice bleDevice, BleException exception) {
                        KLog.e(exception.toString());
                        if (!emitter.isDisposed()) {
                            emitter.onError(new com.eternal.base.exception.BleException(exception.toString()));
                        }
                    }

                    @Override
                    public void onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status) {
                        KLog.i(bleDevice + "连接成功！");
                        if (emitter.isDisposed()) {
//                            manager.disconnect(bleDevice);
                        } else {
                            emitter.onSuccess(bleDevice);
                        }
                    }

                    @Override
                    public void onDisConnected(boolean isActiveDisConnected, BleDevice device, BluetoothGatt gatt, int status) {
                        KLog.i(device + "断开连接！");
                        listener.onDisconnect(device);
                    }
                });
            }
        });
    }

    Single<BleStatue> notify(final BleDevice device) {
        return Single.create(new SingleOnSubscribe<BleStatue>() {
            @Override
            public void subscribe(final SingleEmitter<BleStatue> emitter) throws Exception {
                manager.notify(device, BluetoothKey.UUID_SERVICE, BluetoothKey.UUID_NOTIFY, new NotifyCallback(device) {
                    @Override
                    public void onNotifySuccess() {
                        KLog.e("onNotifySuccess:" + ByteUtils.bytes2HexString(getMessages().getPacket()));
                        emitter.onSuccess(getMessages());
                    }

                    @Override
                    public void onNotifyFailure(BleException exception) {
                        KLog.e("onNotifyFailure:" + exception);
                        emitter.onError(new com.eternal.base.exception.BleException(exception.toString()));
                    }
                });
            }
        });
    }

    void tryWrite(BleStatue queue, Message message) {
        if (queue.offer(message)) {
            write(queue);
        }
    }

    private void write(final BleStatue queue) {
        if (!manager.isConnected(queue.getDevice()) || queue.getPacket().length == 0) {
            return;
        }
//        KLog.e("writing:" + ByteUtils.bytes2HexString(queue.getPacket()));

        android.os.Message message = mainHandler.obtainMessage();
        message.what = MSG_CHA_READ_TIME;
        message.obj = queue;
        mainHandler.sendMessageDelayed(message,  1000*8);

        manager.write(queue.getDevice(), BluetoothKey.UUID_SERVICE,
                BluetoothKey.UUID_WRITE, queue.getPacket(), false, new BleWriteCallback() {
                    @Override
                    public void onWriteSuccess(int current, int total, byte[] justWrite) {
                        KLog.i(ByteUtils.bytes2HexString(justWrite));
                    }

                    @Override
                    public void onWriteFailure(BleException exception) {
                        mainHandler.removeMessages(MSG_CHA_READ_TIME,queue);
                        if(exception.getCode()==BleException.ERROR_CODE_OTHER){
                            try {
                                Thread.sleep(50);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            KLog.e(exception.toString());
                            write(queue);
                        }else{
                            KLog.e(exception.toString());
                        }
                    }
                });
    }

    void onComplement(BleStatue queue) {
        if (queue.onComplement()) {
            write(queue);
        }
    }

    void dispose(BleStatue queue, ObservableMessage message) {
        if (queue.remove(message)) {
            write(queue);
        }
    }

    public void clear(BleDevice device) {
        manager.disconnect(device);
    }

    Single<Integer> setMtu(final BleDevice device, final int mtu) {
        return Single.create(new SingleOnSubscribe<Integer>() {
            @Override
            public void subscribe(final SingleEmitter<Integer> emitter) throws Exception {
                manager.setMtu(device, mtu, new BleMtuChangedCallback() {
                    @Override
                    public void onSetMTUFailure(BleException exception) {
                        emitter.onError(new com.eternal.base.exception.BleException(exception.toString()));
                    }

                    @Override
                    public void onMtuChanged(int mtu) {
                        emitter.onSuccess(mtu);
                    }
                });
            }
        });
    }

    abstract class NotifyCallback extends BleNotifyCallback implements StateMachine.Callback {
        private final BleStatue messages;
        private final StateMachine machine;

        public NotifyCallback(BleDevice device) {
            this.messages = new BleStatue(device);
            machine = new StateMachine(this);
        }

        protected BleStatue getMessages() {
            return messages;
        }

        @Override
        public void onCharacteristicChanged(byte[] data) {
            machine.onNext(data);
        }

        @Override
        public void onPacket(byte[] packet) {
            if (packet[0] == 0x1e && packet[1] == (byte) 0xff) {
                messages.onNext(CFamilialResolution.parseInfo(packet));
            } else {
                mainHandler.removeMessages(MSG_CHA_READ_TIME,messages);
                if (messages.onNext(packet)) {
                    write(messages);
                }
            }
        }
    }

    public interface DisconnectListener {
        /**
         * 断开连接回调
         *
         * @param device 断开的设备
         */
        void onDisconnect(BleDevice device);
    }
}
